package com.run_java_test.java_local_run;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class DynamicClassManagerTest {

    private final Map<String, ClassLoader> classLoaders = new HashMap<>();
    private final Map<String, Object> classInstances = new HashMap<>();

    // Compile a Java file
    public boolean compileJavaFile(String filePath) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
            Iterable<? extends javax.tools.JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(filePath);
            return compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();
        }
    }

    // Load a class dynamically
    public void loadClass(String className, String classFilePath) throws Exception {
        Path classPath = Paths.get(classFilePath);
        byte[] classBytes = Files.readAllBytes(classPath);

        DynamicClassLoader classLoader = new DynamicClassLoader();
        Class<?> clazz = classLoader.loadClass(className, classBytes);
        Object instance = clazz.getDeclaredConstructor().newInstance();

        // Store class loader and instance
        classLoaders.put(className, classLoader);
        classInstances.put(className, instance);
    }

    // Invoke a method on the loaded class instance
    public Object invokeMethod(String className, String methodName, Object... args) throws Exception {
        if (!classInstances.containsKey(className)) {
            System.out.println("Class not loaded: " + className);
            return null;
        }

        Object instance = classInstances.get(className);
        Class<?> clazz = instance.getClass();

        // Find the method by name and parameters
        Method method = clazz.getMethod(methodName, getParameterTypes(args));
        return method.invoke(instance, args);
    }

    // Unload a class
    public void unloadClass(String className) {
        classLoaders.remove(className);
        classInstances.remove(className);
        System.gc(); // Suggest garbage collection
        System.out.println("Class unloaded: " + className);
    }

    // Helper method to determine parameter types
    private Class<?>[] getParameterTypes(Object[] args) {
        if (args == null || args.length == 0) {
            return new Class<?>[0];
        }

        Class<?>[] paramTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            paramTypes[i] = args[i].getClass();
        }
        return paramTypes;
    }

    // Custom ClassLoader
    static class DynamicClassLoader extends ClassLoader {
        public Class<?> loadClass(String className, byte[] classBytes) {
            return defineClass(className, classBytes, 0, classBytes.length);
        }
    }

    // Test
    public static void main(String[] args) {
        try {
            DynamicClassManagerTest manager = new DynamicClassManagerTest();

            // Compile Java file
            String javaFilePath = "./TestClass.java";
            if (!manager.compileJavaFile(javaFilePath)) {
                System.out.println("Compilation failed.");
                return;
            }

            // Load class
            String className = "TestClass";
            String classFilePath = "./TestClass.class";
            manager.loadClass(className, classFilePath);

            // Invoke method
            Object result = manager.invokeMethod(className, "sayHello", "Dynamic World");
            System.out.println("Method result: " + result);

            // Unload class
            manager.unloadClass(className);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// Example TestClass.java file:
// public class TestClass {
//     public String sayHello(String name) {
//         return "Hello, " + name + "!";
//     }
// }
